﻿/**
 * DataDrivenFramework.cs
 *
 * Copyright (C) 2008,  iron9light
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

using System;
using System.Collections;
using System.Reflection;
using NUnit.Core;

namespace NUnitExtension.DataDriven.AddIn
{
    public static class DataDrivenFramework
    {
        public const string DataFactoryAttribute = "NUnitExtension.DataDriven.DataFactoryAttribute";
        public const string DataSourceAttribute = "NUnitExtension.DataDriven.DataSourceAttribute";
        public const string TestCaseItem = "NUnitExtension.DataDriven.TestCaseItem";
        public const string ThrowsConstraint = "NUnit.Framework.Constraints.ThrowsConstraint";
        public const string ThrowsNothingConstraint = "NUnit.Framework.Constraints.ThrowsNothingConstraint";
        public const string ExpectedExceptionConstraint = "NUnit.Framework.Constraints.ExpectedExceptionConstraint";

        public static bool IsDataDriven(MethodInfo method)
        {
            if (method == null)
                return false;

            return Reflect.HasAttribute(method, DataFactoryAttribute, false); ;
        }

        public static Attribute[] GetDataFactoryAttributes(MethodInfo method)
        {
            if (method == null)
                throw new ArgumentNullException("method");

            return Reflect.GetAttributes(method, DataFactoryAttribute, false);
        }

        public static bool IsTestCaseItem(object obj)
        {
            return Reflect.InheritsFrom(obj, TestCaseItem);
        }

        public static bool IsThrowsConstraint(object obj)
        {
            if(Reflect.InheritsFrom(obj, ThrowsConstraint))
                return true;
            if(Reflect.InheritsFrom(obj, ThrowsNothingConstraint))
                return true;
            if(Reflect.InheritsFrom(obj, ExpectedExceptionConstraint))
                return true;

            return false;
        }

        public static IEnumerable GetDataSource(Type factoryType, object[] args)
        {
            if(factoryType == null)
                throw new ArgumentNullException("factoryType");

            //MethodInfo method = Reflect.GetMethodWithAttribute(factoryType, DataSourceAttribute, true);
            MethodInfo method = GetMethodWithAttrParams(factoryType, DataSourceAttribute, args, true);
            object fixture = null;
            if (!method.IsStatic)
                fixture = Reflect.Construct(factoryType);
            IEnumerable dataSource = Reflect.InvokeMethod(method, fixture, args) as IEnumerable;
            return dataSource;
        }

        public static MethodInfo GetMethodWithAttrParams(Type fixtureType, string attributeName, object[] parameters, bool inherit)
        {
            BindingFlags AllMembers = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
            foreach (MethodInfo method in fixtureType.GetMethods(AllMembers))
            {
                if (Reflect.HasAttribute(method, attributeName, inherit))
                    if (IsValidParameters(method.GetParameters(), parameters))
                        return method;
            }

            return null;
        }

        private static bool IsValidParameters(ParameterInfo[] parameterInfos, object[] parameters)
        {
            if (parameters == null)
                return parameterInfos.Length == 0;

            if (parameterInfos.Length != parameters.Length)
                return false;

            for (int i = 0; i < parameterInfos.Length; i++)
                if (!parameterInfos[i].ParameterType.IsInstanceOfType(parameters[i]))
                    return false;

            return true;
        }

        public static IEnumerable GetTestCaseItems(object factory, object[] args)
        {
            MethodInfo method = Reflect.GetMethodWithAttribute(factory.GetType(), DataSourceAttribute, true);
            IEnumerable testCaseItems = Reflect.InvokeMethod(method, factory, args) as IEnumerable;
            return testCaseItems;
        }

        public static object GetFactory(Attribute attribute)
        {
            Type factoryType = GetFactoryType(attribute);
            if(factoryType == null)
            {
                return null;
            }
            return Reflect.Construct(factoryType);
        }

        public static Type GetFactoryType(Attribute attribute)
        {
            return Reflect.GetPropertyValue(attribute, "FactoryType") as Type;
        }

        public static object[] GetArguments(Attribute attribute)
        {
            return Reflect.GetPropertyValue(attribute, "Arguments") as object[];
        }

        public static object[] GetArguments(object testCaseItem)
        {
            return Reflect.GetPropertyValue(testCaseItem, "Arguments") as object[];
        }

        public static IEnumerable GetConstraints(object testCaseItem)
        {
            return Reflect.GetPropertyValue(testCaseItem, "Constraints") as IEnumerable;
        }

        public static object GetPropertyValue(object obj, string name)
        {
            if(obj == null)
                throw new ArgumentNullException("obj");
            if(name == null)
                throw new ArgumentNullException("name");

            return Reflect.GetPropertyValue(obj, name);
        }
    }

    public class NUnitFramework
    {
        public static readonly string AssertType = "NUnit.Framework.Assert";
        public static readonly string ConstraintType = "NUnit.Framework.Constraints.Constraint";
        public static readonly string TestDelegateType = "NUnit.Framework.TestDelegate";

        private static Assembly frameworkAssembly;
        private static Assembly FrameworkAssembly
        {
            get
            {
                if (frameworkAssembly == null)
                    foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
                        if (assembly.GetName().Name == "nunit.framework")
                        {
                            frameworkAssembly = assembly;
                            break;
                        }

                return frameworkAssembly;
            }
        }

        /// <summary>
        /// NUnitFramework.Assert is a nested class that implements
        /// a few of the framework operations by reflection, 
        /// using whatever framework version is available.
        /// </summary>
        public class Assert
        {
            #region Properties
            private static Type assertType;
            private static Type AssertType
            {
                get
                {
                    if (assertType == null)
                        assertType = FrameworkAssembly.GetType(NUnitFramework.AssertType);

                    return assertType;
                }
            }

            private static Type constraintType;
            private static Type ConstraintType
            {
                get
                {
                    if (constraintType == null)
                        constraintType = FrameworkAssembly.GetType(NUnitFramework.ConstraintType);

                    return constraintType;
                }
            }

            private static Type testDelegateType;
            private static Type TestDelegateType
            {
                get
                {
                    if (testDelegateType == null)
                        testDelegateType = FrameworkAssembly.GetType(NUnitFramework.TestDelegateType);

                    return testDelegateType;
                }
            }

            //private static MethodInfo areEqualMethod;
            //private static MethodInfo AreEqualMethod
            //{
            //    get
            //    {
            //        if (areEqualMethod == null)
            //            areEqualMethod = AssertType.GetMethod(
            //                "AreEqual",
            //                BindingFlags.Static | BindingFlags.Public,
            //                null,
            //                new Type[] { typeof(object), typeof(object) },
            //                null);

            //        return areEqualMethod;
            //    }
            //}
            private static MethodInfo thatMethod;
            private static MethodInfo ThatMathod
            {
                get
                {
                    if(thatMethod == null)
                    {
                        thatMethod = AssertType.GetMethod(
                            "That",
                            BindingFlags.Static | BindingFlags.Public,
                            null,
                            new Type[] {typeof (object), ConstraintType},
                            null);
                    }
                    return thatMethod;
                }
            }

            //private static PropertyInfo counterProperty;
            //private static PropertyInfo CounterProperty
            //{
            //    get
            //    {
            //        if (counterProperty == null)
            //            counterProperty = Reflect.GetNamedProperty(
            //                AssertType,
            //                "Counter",
            //                BindingFlags.Public | BindingFlags.Static);

            //        return counterProperty;
            //    }
            //}
            #endregion

            ///// <summary>
            ///// Invoke Assert.AreEqual by reflection
            ///// </summary>
            ///// <param name="expected">The expected value</param>
            ///// <param name="actual">The actual value</param>
            //public static void AreEqual(object expected, object actual)
            //{
            //    if (AreEqualMethod != null)
            //        AreEqualMethod.Invoke(null, new object[] { expected, actual });
            //}

            ///// <summary>
            ///// Get the assertion counter. It clears itself automatically
            ///// on each call.
            ///// </summary>
            ///// <returns>Count of number of asserts since last call</returns>
            //public static int GetAssertCount()
            //{
            //    return CounterProperty == null
            //        ? 0
            //        : (int)CounterProperty.GetValue(null, new object[0]);
            //}

            public static void That(object actual, object constraint)
            {
                if (ThatMathod != null)
                    //ThatMathod.Invoke(null, new object[]{actual, constraint});
                    Reflect.InvokeMethod(ThatMathod, null, actual, constraint);
            }

            public static object CreateTestDelegate(object target, string method)
            {
                return Delegate.CreateDelegate(TestDelegateType, target, method);
            }
        }
    }
}
